import os
import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select, WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.edge.service import Service as EdgeService
from selenium.webdriver.firefox.service import Service as FirefoxService
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from webdriver_manager.firefox import GeckoDriverManager


class Web_Browser:
    def __init__(self, mode: int = 0, headless: bool = False):
        """
        操作浏览器，选择模式
        :param mode: 操作模式，0==edge,1==chrome,2==firefox, 其他==ie
        :param headless: 是否以无头模式运行浏览器，True 表示不显示浏览器，False 表示显示浏览器
        """
        browsers = {
            0: webdriver.Edge,
            1: webdriver.Chrome,
            2: webdriver.Firefox
        }
        browser_class = browsers.get(mode, webdriver.Ie)
        options = self._get_browser_options(browser_class, headless)

        if browser_class == webdriver.Chrome:
            service = Service(ChromeDriverManager().install())
            self.driver = browser_class(service=service, options=options) if options else browser_class(service=service)
        elif browser_class == webdriver.Edge:
            service = EdgeService(EdgeChromiumDriverManager().install())
            self.driver = browser_class(service=service, options=options) if options else browser_class(service=service)
        elif browser_class == webdriver.Firefox:
            service = FirefoxService(GeckoDriverManager().install())
            self.driver = browser_class(service=service, options=options) if options else browser_class(service=service)
        else:
            self.driver = browser_class(options=options) if options else browser_class()
        # 最大化浏览器窗口
        self.driver.maximize_window()

    def _get_browser_options(self, browser_class, headless):
        os.makedirs('./download_files', exist_ok=True)
        current_path = f'{os.getcwd()}/download_files'.replace('/', '\\')
        # print(current_path)
        if headless:
            if browser_class == webdriver.Edge:
                options = webdriver.EdgeOptions()
                options.add_argument('--headless')
                options.add_argument('--ignore-certificate-errors')
                options.add_argument("--disable-smartscreen")
                prefs = {
                    "download.default_directory": current_path,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": True,
                    "safebrowsing.enabled": True,
                    "credentials_enable_service": False,
                    "profile.password_manager_enabled": False,
                    "autofill.profile_enabled": False,
                }
                options.add_experimental_option("prefs", prefs)
                return options
            elif browser_class == webdriver.Chrome:
                options = webdriver.ChromeOptions()
                options.add_argument('--headless')
                options.add_argument('--ignore-certificate-errors')
                options.add_argument("--disable-smartscreen")
                prefs = {
                    "download.default_directory": current_path,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": True,
                    "safebrowsing.enabled": True,
                    "credentials_enable_service": False,
                    "profile.password_manager_enabled": False,
                    "autofill.profile_enabled": False,
                }
                options.add_experimental_option("prefs", prefs)
                return options
            elif browser_class == webdriver.Firefox:
                options = webdriver.FirefoxOptions()
                options.add_argument('-headless')
                options.add_argument('--ignore-certificate-errors')
                options.set_preference("browser.download.folderList", 2)
                options.set_preference("browser.download.dir", current_path)
                options.set_preference("browser.download.manager.showWhenStarting", False)
                options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
                options.set_preference("signon.rememberSignons", False)
                options.set_preference("browser.formfill.enable", False)
                return options
        else:
            if browser_class == webdriver.Edge:
                options = webdriver.EdgeOptions()
                options.add_argument("--disable-smartscreen")
                prefs = {
                    "download.default_directory": current_path,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": False,
                    "safebrowsing.enabled": False,
                    "credentials_enable_service": False,
                    "profile.password_manager_enabled": False,
                    "autofill.profile_enabled": False,
                }
                options.add_experimental_option("prefs", prefs)
                return options
            elif browser_class == webdriver.Chrome:
                options = webdriver.ChromeOptions()
                prefs = {
                    "download.default_directory": current_path,
                    "download.prompt_for_download": False,
                    "download.directory_upgrade": True,
                    "safebrowsing.enabled": False,
                    "credentials_enable_service": False,
                    "profile.password_manager_enabled": False,
                    "autofill.profile_enabled": False,
                }
                options.add_experimental_option("prefs", prefs)
                return options
            elif browser_class == webdriver.Firefox:
                options = webdriver.FirefoxOptions()
                options.set_preference("browser.download.folderList", 2)
                options.set_preference("browser.download.dir", current_path)
                options.set_preference("browser.download.manager.showWhenStarting", False)
                options.set_preference("browser.helperApps.neverAsk.saveToDisk", "application/octet-stream")
                options.set_preference("signon.rememberSignons", False)
                options.set_preference("browser.formfill.enable", False)
                return options
        return None

    def __del__(self):
        self.close()

    def close(self):
        self.driver.quit()

    def visit_url(self, url):
        self.driver.get(url)

    # def dingding_login(self, url, by, flag, tt: int = 60):
    def dingding_login(self,
                       url="https://login.dingtalk.com/oauth2/challenge.htm?spm=dd_developers.homepage.0.0.205a4a97lu7uJM&redirect_uri=https%3A%2F%2Fopen.dingtalk.com%2Fdingtalk_sso_call_back%3Fcontinue%3Dhttps%3A%2F%2Fopen.dingtalk.com%2F&response_type=code&client_id=dingbakuoyxavyp5ruxw&scope=openid+corpid"):
        """
        登陆，填入判断是否登陆整个的组件值
        :param url:目标网址
        :param by:方式
        :param flag:标识
        :param tt:失效时间，默认60秒
        :return:
        """
        self.visit_url(url)
        tp = 0
        t = 1
        tt = 90
        while not self.find_element(2, '//*[@id="root"]/section/div[1]/div/div/div/div[2]/div[1]/a', 1):
            if t > tt:
                t = 1
                tp += 1
                if tp > 2:
                    return False
                self.driver.refresh()
            # print(t)
            t += 1
        return True

    def find_element(self, by, element, time=0, mode=0, index=0):
        """
        查找元素的方法
        :param by: 查找元素的方式 (1-8种方法)
        :param element: 查找元素的值
        :param time: 时间，=0就直接查找，默认等于0
        :param mode: 显式等待或者隐式等待，默认=0，显式等待
        :param index: 指定使用第几个element，默认第一个
        :return: element对象
        """
        if by == 1:
            by_method = By.ID
        elif by == 2:
            by_method = By.XPATH
        elif by == 3:
            by_method = By.NAME
        elif by == 4:
            by_method = By.CLASS_NAME
        elif by == 5:
            by_method = By.TAG_NAME
        elif by == 6:
            by_method = By.LINK_TEXT
        elif by == 7:
            by_method = By.PARTIAL_LINK_TEXT
        elif by == 8:
            by_method = By.CSS_SELECTOR
        else:
            raise ValueError("不支持的查找方式，请输入1 - 8之间的数字。")
        try:
            if time == 0:
                elements = self.driver.find_elements(by_method, element)
            elif mode == 0:  # 显式等待
                wait = WebDriverWait(self.driver, time)
                elements = wait.until(EC.presence_of_all_elements_located((by_method, element)))
            else:  # 隐式等待
                self.driver.implicitly_wait(time)
                elements = self.driver.find_elements(by_method, element)

            # print(elements)
            if index < len(elements):
                return elements[index]
            else:
                return None
        except Exception as e:
            # print(f'查找元素出错：{e}')
            return None

    def get_cookies(self):
        """
        获取当前页面的 cookies
        :return: 包含 cookies 信息的列表
        """
        try:
            cookies = self.driver.get_cookies()
            print("成功获取到cookies")
            return cookies
        except Exception as e:
            print(f"获取cookies失败: {e}")
            return []

    def set_cookies(self, cookies):
        """
        设置 cookies
        :param cookies: 包含 cookies 信息的列表
        """
        try:
            for cookie in cookies:
                self.driver.add_cookie(cookie)
            print("成功设置cookies")
        except Exception as e:
            print(f"设置cookies失败: {e}")

    def snapshot(self, save_path, is_all_browser=True, *area, ):
        """
        进行快照
        :param save_path: 保存位置
        :param is_all_browser: 是否快照整个浏览器，默认True
        :param *area: 区域，element 单个照或者整个浏览器，按照find_element参数填

        """
        # print(area)
        try:
            if is_all_browser:
                result = self.driver.save_screenshot(save_path)
            else:
                if len(area) < 2:
                    print("指定元素查找参数不足，无法进行快照。")
                    return
                element = self.find_element(*area)
                if element:
                    result = element.screenshot(save_path)
                else:
                    print("未找到指定元素，无法进行快照。")
                    return
            if result:
                print(f"快照已成功保存到 {save_path}")
            else:
                print("快照保存失败。")
        except Exception as e:
            print(f"快照过程中出现错误: {e}")

    def show_sub_elements(self, by, element, time=0, mode=0):
        """
        显示指定元素的下级元素，并返回xpath字典
        :param by: 查找元素的方式 (1-8种方法)
        :param element: 查找元素的值
        :param time: 时间，=0就直接查找，默认等于0
        :param mode: 显式等待或者隐式等待，默认=0，显式等待
        :return: 包含下级元素xpath的字典
        """
        parent_element = self.find_element(by, element, time, mode)
        if not parent_element:
            print("未找到指定的父元素。")
            return {}

        sub_elements = parent_element.find_elements(By.XPATH, './*')
        xpath_dict = {}
        for index, sub_element in enumerate(sub_elements):
            # xpath = f"{self.get_element_xpath(parent_element)}/{self.get_element_xpath(sub_element)}"
            xpath = f"{self.get_element_xpath(sub_element)}"
            xpath_dict[f"sub_element_{index + 1}"] = xpath
            # print(f"下级元素 {index + 1} 的 XPath: {xpath}")
        return xpath_dict

    def get_element_xpath(self, element):
        """
        获取元素的 XPath
        :param element: 元素对象
        :return: 元素的 XPath
        """
        return self.driver.execute_script('''
              function getElementXPath(element) {
                  if (element && element.id) {
                      return '//*[@id="' + element.id + '"]';
                  }
                  var paths = [];
                  for (; element && element.nodeType === 1; element = element.parentNode) {
                      var index = 0;
                      for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling) {
                          if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
                              index++;
                          }
                      }
                      var tagName = element.tagName.toLowerCase();
                      var pathIndex = (index ? "[" + (index + 1) + "]" : "");
                      paths.splice(0, 0, tagName + pathIndex);
                  }
                  return paths.length ? "/" + paths.join("/") : null;
              }
              return getElementXPath(arguments[0]);
          ''', element)

    def find_tag_xpaths(self, tag_name):
        """
        找出指定 tag_name 的元素，并返回其 XPath 列表，同时打印元素信息
        :param tag_name: 要查找的标签名
        :return: 包含匹配元素 XPath 的列表
        """
        elements = self.driver.find_elements(By.TAG_NAME, tag_name)
        xpaths = []
        for element in elements:
            xpath = self.get_element_xpath(element)
            text = element.text.strip()
            print(f"元素文本内容: {text}, XPath: {xpath}")
            xpaths.append(xpath)
        return xpaths

    def action_element(self, element):
        # 滚动到目标元素
        actions = ActionChains(self.driver)
        actions.move_to_element(element).perform()

    # 选择下拉框选项
    def select_option(self, by, element, option_text, time=0, mode=0):
        """
        选择下拉框选项
        :param by: 查找下拉框元素的方式 (1-8种方法)
        :param element: 查找下拉框元素的值
        :param option_text: 要选择的选项文本
        :param time: 时间，=0就直接查找，默认等于0
        :param mode: 显式等待或者隐式等待，默认=0，显式等待
        """
        select_element = self.find_element(by, element, time, mode)
        if select_element:
            try:
                select = Select(select_element)
                select.select_by_visible_text(option_text)
                # print(f"成功选择选项: {option_text}")
            except Exception as e:
                print(f"选择选项失败: {e}")
        else:
            print("未找到下拉框元素，无法选择选项。")

    def submit_form(self, by, element, time=0, mode=0):
        """
        提交表单
        :param by: 查找表单元素的方式 (1-8种方法)
        :param element: 查找表单元素的值
        :param time: 时间，=0就直接查找，默认等于0
        :param mode: 显式等待或者隐式等待，默认=0，显式等待
        """
        form_element = self.find_element(by, element, time, mode)
        if form_element:
            try:
                form_element.submit()
                # print("表单提交成功")
            except Exception as e:
                print(f"表单提交失败: {e}")
        else:
            print("未找到表单元素，无法提交表单。")

    def wait_for_download(self, file_name, timeout=1000):
        """
        等待文件下载完成
        :param file_name: 要等待的文件名
        :param timeout: 最大等待时间，默认1000秒
        :return: 下载成功返回True，超时返回False
        """
        download_path = os.getcwd()
        start_time = time.time()
        while time.time() - start_time < timeout:
            file_path = os.path.join(download_path, file_name)
            if os.path.exists(file_path):
                try:
                    # 尝试重命名文件，若能成功则表示文件未被占用，下载完成
                    os.rename(file_path, file_path)
                    return True
                except PermissionError:
                    # 文件正在被占用，继续等待
                    time.sleep(1)
            else:
                time.sleep(1)
        return False

    def move_to_and_click(self, by, element, time=0, mode=0, click=True):
        """
        移动到指定组件，可选择是否点击
        :param by: 查找元素的方式 (1-8种方法)
        :param element: 查找元素的值
        :param time: 时间，=0就直接查找，默认等于0
        :param mode: 显式等待或者隐式等待，默认=0，显式等待
        :param click: 是否点击元素，默认True
        """
        target_element = self.find_element(by, element, time, mode)
        if target_element:
            try:
                actions = ActionChains(self.driver)
                actions.move_to_element(target_element)
                if click:
                    actions.click()
                actions.perform()
                if click:
                    print("成功移动到元素并点击")
                else:
                    print("成功移动到元素")
            except Exception as e:
                print(f"操作失败: {e}")
        else:
            print("未找到指定元素，无法进行操作。")

    def list_xpaths_by_tag(self, tag_name):
        """
        列出指定tag_name的所有xpath，返回字典
        :param tag_name: 要查找的标签名
        :return: 包含匹配元素XPath的字典
        """
        elements = self.driver.find_elements(By.TAG_NAME, tag_name)
        xpath_dict = {}
        for index, element in enumerate(elements):
            xpath = self.get_element_xpath(element)
            xpath_dict[f"element_{index + 1}"] = xpath
        return xpath_dict
